/*
 * Copyright 2000-2006 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.gwt.references;

import com.intellij.openapi.components.ProjectComponent;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.filters.position.ParentElementFilter;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.gwt.module.GwtModulesManager;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;

/**
 * @author nik
 */
public class GwtReferencesProvidersRegistry implements ProjectComponent {
  private ReferenceProvidersRegistry myRegistry;

  public GwtReferencesProvidersRegistry(ReferenceProvidersRegistry registry) {
    myRegistry = registry;
  }

  public void projectOpened() {
  }

  public void projectClosed() {
  }

  @NonNls @NotNull
  public String getComponentName() {
    return "GwtReferencesProvidersRegistry";
  }

  public void initComponent() {
    myRegistry.registerReferenceProvider(
      new ParentElementFilter(new PsiMethodCallFilter("com.google.gwt.user.client.ui.RootPanel", "get"), 2),
      PsiLiteralExpression.class, new GwtToHtmlReferencesProvider());

    myRegistry.registerReferenceProvider(
      new ParentElementFilter(new PsiMethodCallFilter("com.google.gwt.user.client.ui.UIObject",
                                                      "addStyleName", "removeStyleName", "setStyleName"), 2),
      PsiLiteralExpression.class, new GwtToCssClassReferenceProvider());

    myRegistry.registerXmlAttributeValueReferenceProvider(
      new String[]{"name"}, new ParentElementFilter(new InheritsTagFilter(), 2),
      true,
      new GwtModuleReferencesProvider()
    );
  }

  public void disposeComponent() {
  }

  private static class PsiMethodCallFilter implements ElementFilter {
    private @NonNls String myClassName;
    private @NonNls Set<String> myMethodNames;


    public PsiMethodCallFilter(@NonNls final String className, @NonNls final String... methodNames) {
      myClassName = className;
      myMethodNames = new HashSet<String>(Arrays.asList(methodNames));
    }

    public boolean isAcceptable(Object element, PsiElement context) {
      if (element instanceof PsiMethodCallExpression) {
        final PsiMethodCallExpression callExpression = (PsiMethodCallExpression)element;
        final PsiMethod psiMethod = callExpression.resolveMethod();
        if (psiMethod != null) {
          if (!myMethodNames.contains(psiMethod.getName())) {
            return false;
          }
          final PsiClass psiClass = psiMethod.getContainingClass();
          final PsiClass expectedClass = PsiManager.getInstance(psiClass.getProject()).findClass(myClassName, psiClass.getResolveScope());
          return InheritanceUtil.isInheritorOrSelf(psiClass, expectedClass, true);
        }
      }
      return false;
    }

    public boolean isClassAcceptable(Class hintClass) {
      return PsiMethodCallExpression.class.isAssignableFrom(hintClass);
    }

    public @NonNls String toString() {
      return "methodcall(" + myClassName + "." + myMethodNames + ")";
    }
  }

  private static class InheritsTagFilter implements ElementFilter {
    public boolean isAcceptable(Object element, PsiElement context) {
      if (!(element instanceof XmlTag)) {
        return false;
      }

      XmlTag tag = (XmlTag)element;
      if (!"inherits".equals(tag.getLocalName())) return false;

      final XmlTag parentTag = tag.getParentTag();
      if (parentTag == null || !"module".equals(parentTag.getLocalName())) return false;

      final String fileName = parentTag.getContainingFile().getName();
      return fileName != null && fileName.endsWith(GwtModulesManager.GWT_XML_SUFFIX);
    }

    public boolean isClassAcceptable(Class hintClass) {
      return XmlTag.class.isAssignableFrom(hintClass);
    }
  }
}
